package org.baicaixiaozhan.learncommon.util;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.baicaixiaozhan.learncommon.util.annotation.Beta;
import org.baicaixiaozhan.learncommon.util.annotation.BetaType;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
 * DESC: redis lua 脚本执行器默认实现
 *
 * @since 2021-07-07
 * @author baicaixiaozhan
 */
@Slf4j
@AllArgsConstructor
@Component("defaultRedisScriptExecutor")
public class DefaultRedisScriptExecutor implements RedisScriptExecutor, RecoveryCallback<RuntimeException>,
        InitializingBean {

    /**
     * lua 脚本地址
     */
    public static final String LOCATION_PATTERN = "/lua/*.lua";
    /**
     * Redis Script Map
     */
    private Map<String, RedisScript<Boolean>> redisScriptMap;

    private final StringRedisTemplate stringRedisTemplate;

    @Override
    public void afterPropertiesSet() throws Exception {
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resourcePatternResolver.getResources(LOCATION_PATTERN);
        redisScriptMap = new ConcurrentHashMap<>(resources.length);
        for (Resource resource : resources) {
            redisScriptMap.put(SimpleFileUtils.getRealFileNameInFullName(Objects.requireNonNull(resource.getFilename())),
                    loadRedisScript(resource));
        }
    }

    private RedisScript<Boolean> loadRedisScript(Resource resource) {
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setLocation(resource);
        redisScript.setResultType(Boolean.class);
        return redisScript;
    }

    @Beta(type = BetaType.UNSTABLE)
    private <R> RedisScript<R> loadRedisScript(Resource resource, Class<R> resultType) {
        DefaultRedisScript<R> redisScript = new DefaultRedisScript<>();
        redisScript.setLocation(resource);
        redisScript.setResultType(resultType);
        return redisScript;
    }

    private RedisScript<Boolean> getRedisScript(String scriptName) {
        return redisScriptMap.get(scriptName);
    }

    @Retryable(
            maxAttempts = 2,
            backoff = @Backoff(delay = 2000),
            value = RuntimeException.class,
            recover = "recover"
    )
    @Override
    public <R> Optional<R> execRedisScript(String scriptName, List<String> key, Object... args) {
        return Optional.ofNullable((R) stringRedisTemplate.execute(getRedisScript(scriptName), key, args));
    }

    @Recover
    @Override
    public <R> Optional<R> recover(RuntimeException e, String scriptName, List<String> key, Object... args) {
        if (log.isErrorEnabled()) {
            log.error("execute redis script error message : {}", e.getMessage());
        }
        return Optional.empty();
    }

}
